package com.axapoint.opoc.service;

import generated.Aktor;
import generated.Attribut;
import generated.ObjektList;
import generated.OrgEnhed;
import generated.Organisation;
import generated.OrganisationAttributer;
import generated.OrganisationList;
import generated.OrganisationRelationer;
import generated.Registrering;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.transform.stream.StreamSource;

import org.joda.time.DateTime;
import org.joda.time.Interval;

import com.anasoft.os.daofusion.bitemporal.TimeUtils;
import com.axapoint.opoc.core.dao.Organization;
import com.axapoint.opoc.core.dao.OrganizationFacade;
import com.axapoint.opoc.core.dao.OrganizationType;
import com.axapoint.opoc.core.dao.OrganizationTypeFacade;
import com.axapoint.opoc.core.dao.Unit;
import com.axapoint.opoc.core.model.util.HibernateUtil;

// Map model <-> OPOC xsd'er
public class OrganisationMapper extends AbstractMapper<OrganizationFacade> {

	private final OrgEnhedMapper orgEnhedMapper;
	
	public OrganisationMapper(String systemId, String userId) {
		super(new OrganizationFacade(systemId, userId));
		this.orgEnhedMapper = new OrgEnhedMapper(systemId, userId, this);
	}
	
	OrganisationMapper(String systemId, String userId, OrgEnhedMapper orgEnhedMapper) {
		super(new OrganizationFacade(systemId, userId));
		this.orgEnhedMapper = orgEnhedMapper;
	}

	// Hent snapshot af flere uuid'er
	public ObjektList hentFlere(String[] uuids, DateTime vTime, DateTime rTime, int depth) throws Exception {

		if (rTime == null) rTime = new DateTime();
		if (vTime == null) vTime = new DateTime();
		
		// if uuids are null then all
		TimeUtils.setReference(rTime);
		Organization[] orgz = null;
		if (uuids == null) {
			orgz = facade.getAllEntities();
		} else {
			ArrayList<Organization> list = new ArrayList<Organization>();
			for (String uuid : uuids) {
				list.add(facade.getEntity(uuid));
			}
		}
		OrganisationList result = new OrganisationList();
		for (Organization o : orgz) {
			result.getOrganisation().add(hent(o.getUuid(), vTime, rTime, depth));
		}
		
		return result;
	}
	
	// Hent snapshot 
	public Organisation hent(String uuid, DateTime vTime, DateTime rTime, int depth) throws DatatypeConfigurationException  {
		OrganizationFacade orgFacade = new OrganizationFacade(systemId, userId);
		Organisation xmlObj = new Organisation();
		System.out.println("---- DEBUG: uuid=" + uuid);
		Organization o = orgFacade.getEntity(uuid);
		
		xmlObj.setUuid(o.getUuid());
		xmlObj.setType(xmlObj.getClass().getSimpleName());
		if (depth == 0) return xmlObj;

		if (rTime == null) rTime = new DateTime();
		TimeUtils.setReference(rTime);
		
		Organization.Bitemporal bi = o.getValues().get(vTime);

		// Attributer
		OrganisationAttributer attr = new OrganisationAttributer();
		xmlObj.getAttribut().add(attr);
		attr.setNavn(bi.getValue().getName());
		attr.setFra(dateTimeToXMLGregorianCalendar(bi.getValidityInterval().getStart()));
		attr.setTil(dateTimeToXMLGregorianCalendar(bi.getValidityInterval().getEnd()));

		// Relationer
		Unit.Value[] unitValues = orgFacade.getRootUnits(o.getUuid(), vTime);
		if (unitValues != null && unitValues.length > 0) {
			OrganisationRelationer rel = new OrganisationRelationer();
			xmlObj.getRelation().add(rel);
			rel.setOverordnetOrgEnhed(orgEnhedMapper.hent(unitValues[0].getEntity().getUuid(), vTime, rTime, depth - 1));
		}
		return xmlObj;
	}
	
	
	public Organisation hentHistorik(String uuid, DateTime registreringsTime, Interval vInterval, int depth) throws Exception {
		List<Organization.Bitemporal> list = facade.getHistory(uuid, registreringsTime);
		
		Organisation xmlObj = new Organisation();

		Organization o = facade.getEntity(uuid);
		
		xmlObj.setUuid(o.getUuid());
		xmlObj.setType(xmlObj.getClass().getSimpleName());
		if (depth == 0) return xmlObj;

		for (Organization.Bitemporal bi : list) {

			// Filter by valid interval - optional
			if (vInterval != null && !bi.getValidityInterval().overlaps(vInterval)) {
				continue;
			}
			
			Registrering reg = new Registrering();
			Aktor aktor = new Aktor();
			aktor.setSystemId(systemId);
			aktor.setUserId(userId);
			reg.setAktor(aktor);
			reg.setKommentar(bi.getValue().getComment());
			reg.setFra(dateTimeToXMLGregorianCalendar(bi.getRecordInterval().getStart()));
			reg.setFra(dateTimeToXMLGregorianCalendar(bi.getRecordInterval().getEnd()));

			// Attributer
			OrganisationAttributer attr = new OrganisationAttributer();
			xmlObj.getAttribut().add(attr);
			attr.setNavn(bi.getValue().getName());
			attr.setFra(dateTimeToXMLGregorianCalendar(bi.getValidityInterval().getStart()));
			attr.setTil(dateTimeToXMLGregorianCalendar(bi.getValidityInterval().getEnd()));
			reg.getAttribut().add(attr);

			// Relationer
			Unit.Value[] unitValues = facade.getRootUnits(o.getUuid(), bi.getValidityInterval().getStart());
			if (unitValues != null && unitValues.length > 0) {
				OrgEnhed xmlOrgEnhed = orgEnhedMapper.hentHistorik(unitValues[0].getEntity().getUuid(), registreringsTime, bi.getValidityInterval(), depth - 1);
				if (xmlOrgEnhed != null) {
					OrganisationRelationer rel = new OrganisationRelationer();
					rel.setFra(dateTimeToXMLGregorianCalendar(unitValues[0].getBitemporal().getValidityInterval().getStart()));
					rel.setTil(dateTimeToXMLGregorianCalendar(unitValues[0].getBitemporal().getValidityInterval().getEnd()));
					rel.setOverordnetOrgEnhed(xmlOrgEnhed);
				    reg.getRelation().add(rel);
				}
			}
			xmlObj.getRegistrering().add(reg);
		}
		if (xmlObj.getRegistrering().size() == 0) return null;
		return xmlObj;
	}
	
	public Organisation hentUdvikling(String uuid, DateTime virkningsTime, Interval rInterval, int depth) throws Exception {
		List<Organization.Bitemporal> list = facade.getEvolution(uuid, virkningsTime);
		
		Organisation xmlObj = new Organisation();

		Organization o = facade.getEntity(uuid);
		
		xmlObj.setUuid(o.getUuid());
		xmlObj.setType(xmlObj.getClass().getSimpleName());
		if (depth == 0) return xmlObj;

		for (Organization.Bitemporal bi : list) {

			// Filter by valid interval - optional
			if (rInterval != null && !bi.getRecordInterval().overlaps(rInterval)) {
				continue;
			}
			
			Registrering reg = new Registrering();
			Aktor aktor = new Aktor();
			aktor.setSystemId(systemId);
			aktor.setUserId(userId);
			reg.setAktor(aktor);
			reg.setKommentar(bi.getValue().getComment());
			reg.setFra(dateTimeToXMLGregorianCalendar(bi.getRecordInterval().getStart()));
			reg.setFra(dateTimeToXMLGregorianCalendar(bi.getRecordInterval().getEnd()));

			// Attributer
			OrganisationAttributer attr = new OrganisationAttributer();
			xmlObj.getAttribut().add(attr);
			attr.setNavn(bi.getValue().getName());
			attr.setFra(dateTimeToXMLGregorianCalendar(bi.getValidityInterval().getStart()));
			attr.setTil(dateTimeToXMLGregorianCalendar(bi.getValidityInterval().getEnd()));
			reg.getAttribut().add(attr);

			// Relationer
			Unit.Value[] unitValues = facade.getRootUnits(o.getUuid(), bi.getValidityInterval().getStart());
			if (unitValues != null && unitValues.length > 0) {
				OrgEnhed xmlOrgEnhed = orgEnhedMapper.hentUdvikling(unitValues[0].getEntity().getUuid(), virkningsTime, bi.getRecordInterval(), depth - 1);
				if (xmlOrgEnhed != null) {
					OrganisationRelationer rel = new OrganisationRelationer();
					rel.setFra(dateTimeToXMLGregorianCalendar(unitValues[0].getBitemporal().getValidityInterval().getStart()));
					rel.setTil(dateTimeToXMLGregorianCalendar(unitValues[0].getBitemporal().getValidityInterval().getEnd()));
					rel.setOverordnetOrgEnhed(xmlOrgEnhed);
				    reg.getRelation().add(rel);
				}
			}
			xmlObj.getRegistrering().add(reg);
		}
		if (xmlObj.getRegistrering().size() == 0) return null;
		return xmlObj;
	}

	public Organisation opret(InputStream xmlIs, String kommentar) {
		return createUpdate(xmlIs, kommentar);
	}

	public Organisation importer(InputStream xmlIs, String kommentar) {
		return null;
	}

	private Organisation createUpdate(InputStream xmlIs, String kommentar) {
		String uuid = null;
		
		try {
			HibernateUtil.getCurrentSession().beginTransaction();

			JAXBContext jaxbContext = JAXBContext.newInstance("generated");		
			Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
			JAXBElement<Organisation> obj = unmarshaller.unmarshal(new StreamSource(xmlIs), Organisation.class);
			Organisation inOrg = obj.getValue();
		
			uuid = inOrg.getUuid();
			OrganizationFacade facade = new OrganizationFacade(systemId, userId);
			OrganizationTypeFacade typeFacade = new OrganizationTypeFacade(systemId, userId);
			
			for (Attribut attr : inOrg.getAttribut()) {
				OrganisationAttributer orgAttr = (OrganisationAttributer)attr;
				DateTime fromTime = XMLToDateTime(orgAttr.getFra());
				DateTime toTime = XMLToDateTime(orgAttr.getFra());
				
				// Map type to OrganizationType uuid
				OrganizationType unknownType;
				OrganizationType.Value[] types = typeFacade.findValues("name", "<ukendt>");
				if (types == null || types.length == 0) {
					// Create the unknown org type with valid time of always
					unknownType = typeFacade.create(new OrganizationType.Value("<ukendt>"), "auto create", new Interval(-TimeUtils.END_OF_TIME, TimeUtils.END_OF_TIME));
				} else {
					unknownType = types[0].getEntity();
				}
				
				Organization.Value value = new Organization.Value(orgAttr.getNavn(), unknownType);
				Interval validityInterval = new Interval(fromTime, toTime);
				if (uuid == null) {
					Organization org = facade.create(value, kommentar, validityInterval);
					uuid = org.getUuid();
				} else {
					facade.update(uuid, value, kommentar, validityInterval);
				}
			}
			HibernateUtil.getCurrentSession().getTransaction().commit();

			DateTime dummyTime = new DateTime();

			// Return ref
			return hent(uuid, dummyTime, dummyTime, 0);
		} catch (JAXBException e) {
			e.printStackTrace();
		} catch (DatatypeConfigurationException e) {
			e.printStackTrace();
		}
		
		// Error?
		return null;
	}

}
